Skip to content

feat(shiny): myIOProxy() partial-update API (B5)#82

Merged
mortonanalytics merged 3 commits into
mainfrom
feat/b5-myio-proxy
Jun 22, 2026
Merged

feat(shiny): myIOProxy() partial-update API (B5)#82
mortonanalytics merged 3 commits into
mainfrom
feat/b5-myio-proxy

Conversation

@mortonanalytics

Copy link
Copy Markdown
Owner

B5 — Shiny in-place partial updates

myIOProxy(outputId, session) + updateMyIOData(proxy, label = df) update a rendered chart's layer data without re-running renderMyIO(). The previous reactive pattern destroyed and recreated the chart on every invalidation — flicker, lost brush/zoom/toggle state, full re-layout. The proxy swaps data by layer label and re-renders through the existing data-join path, so only changed marks transition and interaction state is preserved.

output$chart <- renderMyIO({ myIO(df) |> addIoLayer("line", label = "series", mapping = ...) })
observeEvent(input$go, {
  myIOProxy("chart") |> updateMyIOData(series = new_df)
})

Wiring

  • JS: Chart.updateData([{label, data}]) swaps layer data + renderCurrentLayers() (no destroy). An instance registry + a single idempotent myio:proxy-update handler in index.js, populated by the htmlwidget binding in shinyMode and unregistered on re-render.
  • R: myIOProxy() (shiny-guarded, namespaces the id) + updateMyIOData() (row-rectangles each data.frame via the same ensure_source_key/as_layer_rows path and sends the custom message).

Additive — non-Shiny and existing render paths are untouched. Layers matched by label; unknown labels are ignored. Identity data path (transforms aren't re-applied; pass transformed data for transformed layers — documented).

Verification

  • npm test 373 pass (incl. myio-proxy.test.js: data-swap contract + handler routing).
  • npx playwright test proxy.spec.ts: real re-render adds marks, same <svg> node (no destroy), no errors.
  • tests/testthat/test_myIOProxy.R: payload id/namespacing, row shape, multi-layer, validation.
  • R CMD check --as-cran0/0/0.

🤖 Generated with Claude Code

mortonanalytics and others added 3 commits June 21, 2026 22:12
Adds an in-place data-update path for Shiny: myIOProxy(outputId, session) +
updateMyIOData(proxy, label = df) send a 'myio:proxy-update' custom message that
swaps a layer's data by label and re-renders through the existing data-join
(Chart.updateData), instead of destroying+recreating the chart on every reactive
invalidation. Preserves brush/zoom/toggle state and animates only changed marks.

Wiring: an instance registry + idempotent handler in index.js, populated by the
htmlwidget binding in shinyMode and torn down on re-render. Additive; non-Shiny
and existing render paths are untouched.

Tests: tests/js/myio-proxy.test.js (updateData swap + handler routing),
tests/playwright/proxy.spec.ts (real re-render, same svg / no destroy),
tests/testthat/test_myIOProxy.R (payload contract + validation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y preserve

Addresses code + security review:
- SECURITY (proto pollution): null-prototype maps + hasOwnProperty guard for
  Chart.updateData byLabel and window.myIO._instances, so a '__proto__' layer
  label or outputId cannot reach Object.prototype.
- Registry leak/race: register the instance immediately after construction
  (before async coordinator work) so a re-render never leaves an empty window
  that drops an in-flight proxy message; unregister via the chart 'destroy'
  event; lazily reap destroyed charts (config nulled) in the message handler for
  the DOM-removed-without-rerender case.
- Visibility: updateData no longer resets derived.currentLayers, so a
  legend-toggled-off subset stays hidden (mutating shared layer objects already
  updates the visible set) -- matches the 'preserves toggle state' claim.

Tests: proto-pollution + visibility-preserve + dead-chart-reap units; remount+
proxy-update e2e; vi.resetModules() in the handler test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mortonanalytics mortonanalytics merged commit 9367c37 into main Jun 22, 2026
10 checks passed
@mortonanalytics mortonanalytics deleted the feat/b5-myio-proxy branch June 22, 2026 04:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant